import os
from datetime import datetime
from dataclasses import dataclass
from typing import List, Optional, Generator, Tuple
import heapq

@dataclass
class FileInfo:
    path: str
    size: int
    modified_time: float

class FileChunkReader:
    """文件分块读取器类，用于按修改时间顺序读取文件内容。
    
    Attributes:
        directory (str): 要扫描的目录路径
        chunk_size (int): 每次读取的字节数，默认1024字节
        max_files (int): 最多读取的文件数量，0表示读取所有文件
    """
    def __init__(self, directory: str, chunk_size: int = 1024, max_files: int = 0):
        if not os.path.exists(directory):
            raise ValueError(f"目录不存在: {directory}")
        if not os.path.isdir(directory):
            raise ValueError(f"不是有效的目录: {directory}")
        if chunk_size <= 0:
            raise ValueError("chunk_size 必须大于0")
        if max_files < 0:
            raise ValueError("max_files 不能为负数")
        self.directory = directory
        self.chunk_size = chunk_size
        self.max_files = max_files
        self.current_index = 0
        self.current_bytes_read = 0
        self.current_file = None
        self.file_iterator = None
        self._init_file_list()
    
    def reset(self, rescan_files: bool = False) -> None:
        """重置文件读取器状态
        
        Args:
            rescan_files (bool): 是否需要重新扫描文件列表。默认为 False，表示只重置读取位置。
        
        此方法将：
        1. 重置当前文件索引为0
        2. 重置当前已读取字节数为0
        3. 清空当前文件引用
        4. 如果 rescan_files 为 True，则重新初始化文件列表和迭代器
        """
        self.current_index = 0
        self.current_bytes_read = 0
        self.current_file = None
        if rescan_files:
            self._init_file_list()
        else:
            # 只重置迭代器，不重新扫描文件
            self.file_iterator = iter(self.file_list)
            self._move_to_next_file()
    
    def get_file_list(self) -> List[FileInfo]:
        """获取按修改时间排序的文件列表
        
        Returns:
            List[FileInfo]: 文件信息列表，按修改时间从新到旧排序
        """
        return self.file_list
    
    def _init_file_list(self) -> None:
        """初始化文件列表，按修改时间排序"""
        # 使用堆来维护最新的文件
        heap = []
        
        for root, _, filenames in os.walk(self.directory):
            for filename in filenames:
                file_path = os.path.join(root, filename)
                try:
                    modified_time = os.path.getmtime(file_path)
                    file_size = os.path.getsize(file_path)
                    # 跳过大小为0的文件
                    if file_size == 0:
                        print(f"跳过大小为0的文件: {file_path}")
                        continue
                    # 使用负的修改时间，这样堆顶是最新的文件
                    heapq.heappush(heap, (-modified_time, file_path, file_size))
                except (OSError, IOError) as e:
                    print(f"获取文件信息时发生错误 {file_path}: {e}")
                    continue
        
        # 从堆中依次取出文件（按修改时间从新到旧），最多取max_files个
        self.file_list = []
        count = 0
        while heap and (self.max_files == 0 or count < self.max_files):
            _, file_path, file_size = heapq.heappop(heap)
            if not self._is_safe_path(file_path):
                print(f"跳过不安全的文件路径: {file_path}")
                continue
            self.file_list.append(FileInfo(path=file_path, size=file_size, modified_time=-heap[0][0] if heap else 0))
            count += 1
        
        # 初始化迭代器
        self.file_iterator = iter(self.file_list)
        self._move_to_next_file()
    
    def _move_to_next_file(self) -> bool:
        """移动到下一个文件"""
        try:
            self.current_file = next(self.file_iterator)
            self.current_bytes_read = 0
            return True
        except StopIteration:
            # 所有文件都已读取完毕，重新开始
            self._init_file_list()  # 重新枚举文件
            return self.current_file is not None  # 直接返回是否成功获取到文件
    
    def read_next_chunk(self) -> Optional[Tuple[bytes, List[Tuple[str, int, int]]]]:
        """读取下一个数据块，如果当前文件剩余字节不足chunk_size，继续读取下一个文件
        
        Returns:
            Optional[Tuple[bytes, List[Tuple[str, int, int]]]]: 
                - 成功时返回 (数据, [(文件路径, 读取字节数, 起始位置), ...])
                - 失败时返回 None
        """
        if not self.current_file:
            return None
            
        accumulated_data = bytearray()
        file_reads = []  # 记录每个文件的读取情况
        
        while len(accumulated_data) < self.chunk_size:
            if not self.current_file:
                return None
                
            remaining_bytes = self.current_file.size - self.current_bytes_read
            
            try:
                if not self._check_file_permissions(self.current_file.path):
                    print(f"文件无读取权限: {self.current_file.path}")
                    continue
                
                with open(self.current_file.path, 'rb') as f:
                    # 定位到上次读取的位置
                    f.seek(self.current_bytes_read)
                    
                    # 如果当前文件还有数据可读
                    if remaining_bytes > 0:
                        # 计算还需要读取的字节数
                        bytes_needed = self.chunk_size - len(accumulated_data)
                        # 读取所需字节数或剩余字节数
                        bytes_to_read = min(bytes_needed, remaining_bytes)
                        data = f.read(bytes_to_read)
                        self.current_bytes_read += bytes_to_read
                        accumulated_data.extend(data)
                        # 记录当前文件的读取情况，包括起始位置
                        file_reads.append((self.current_file.path, bytes_to_read, self.current_bytes_read - bytes_to_read))
                    
                    # 如果当前文件已读完，移动到下一个文件
                    if remaining_bytes <= bytes_to_read:
                        if not self._move_to_next_file():
                            break
                    else:
                        break
                        
            except (OSError, IOError) as e:
                print(f"无法打开文件 {self.current_file.path}: {e}")
                if not self._move_to_next_file():
                    break
                continue
        
        if accumulated_data:
            return bytes(accumulated_data), file_reads
        return None

    def _is_safe_path(self, file_path: str) -> bool:
        """检查文件路径是否安全"""
        try:
            return os.path.abspath(file_path).startswith(os.path.abspath(self.directory))
        except ValueError:
            return False

    def _check_file_permissions(self, file_path: str) -> bool:
        """检查文件访问权限"""
        try:
            return os.access(file_path, os.R_OK)
        except OSError:
            return False

def format_file_size(size_bytes):
    """将文件大小转换为人类可读的格式"""
    for unit in ['B', 'KB', 'MB', 'GB']:
        if size_bytes < 1024:
            return f"{size_bytes:.2f}{unit}"
        size_bytes /= 1024
    return f"{size_bytes:.2f}TB"

def list_files_by_modified_time(reader: FileChunkReader):
    """列出文件信息
    
    Args:
        reader (FileChunkReader): 文件读取器对象
    """
    file_list = reader.get_file_list()
    
    # 打印结果
    max_files_str = "所有" if reader.max_files == 0 else str(reader.max_files)
    print(f"\n目录 '{reader.directory}' 中的文件（按修改时间从新到旧排序，显示{max_files_str}个）：")
    print("-" * 100)
    
    # 打印文件信息
    for file_info in file_list:
        modified_datetime = datetime.fromtimestamp(file_info.modified_time)
        print(f"{file_info.path:<60} | {modified_datetime.strftime('%Y-%m-%d %H:%M:%S')} | {format_file_size(file_info.size):>10}")
    print("-" * 100)

def main():
    """示例：如何使用FileChunkReader"""
    # 创建文件读取器实例
    reader = FileChunkReader(
        directory=".",  # 当前目录
        chunk_size=4096,  # 每次读取4096字节
        max_files=0  # 读取所有文件
    )
    
    # 显示文件列表
    list_files_by_modified_time(reader)
    
    # 演示读取过程
    print("\n开始演示文件读取过程（前20次读取）：")
    print("-" * 100)
    
    for i in range(20):
        result = reader.read_next_chunk()
        if result:
            data, file_reads = result
            print(f"读取 #{i+1:2d} | 总字节数: {len(data):>3d} | 数据预览: {data[:20].hex()}")
            print("数据来源:")
            for file_path, bytes_read, start_pos in file_reads:
                print(f"  - {file_path:<60} | 读取字节数: {bytes_read:>3d} | 起始位置: {start_pos:>3d}")
        else:
            print("所有文件都已读取完毕")
            break

if __name__ == "__main__":
    main() 